package log

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"sync"
	"time"
)

// logFile is the object for log function
type logFile struct {
	sync.Mutex
	baseName string
	maxSize  int64
	maxFiles int64
	handle   *os.File
	curName  string
	curSize  int64
}

// Open will open log file handle and rotate file if needed
func NewLogFile(baseName string, maxSize, maxFiles int64) (*logFile, error) {
	if maxSize < 1024*1024 {
		maxSize = 1024 * 1024 * 10
	}
	if maxFiles <= 0 {
		maxFiles = 10
	}

	err := EnsureDir(baseName)
	if err != nil {
		return nil, err
	}
	self := &logFile{baseName: baseName, maxSize: maxSize, maxFiles: maxFiles}
	err = self.Rotate()
	go self.LogRotator()
	return self, err
}

// EnsureDir - ensure log dir is already made
func EnsureDir(path string) (err error) {
	dir := filepath.Dir(path)
	err = os.MkdirAll(dir, 0755)
	return
}

// Rotate will create a new log file and write to it
func (lf *logFile) Rotate() error {
	newName := fmt.Sprintf("%s.%s.log", lf.baseName, time.Now().Format("2006-01-02_15:04:05"))
	if newName == lf.curName {
		return nil
	}
	handle, err := os.Create(newName)
	if err != nil {
		return err
	}
	lf.curName = newName
	lf.switchHandle(handle)
	os.Remove(lf.baseName)
	os.Symlink(path.Base(newName), lf.baseName)
	go lf.LogPurge()
	return nil
}

// Close flush all data to file and close the handle
func (lf *logFile) Close() error {
	return lf.switchHandle(nil)
}

func (lf *logFile) switchHandle(newHandle *os.File) error {
	lf.Lock()
	oldHandle := lf.handle
	lf.handle = newHandle
	lf.curSize = 0
	lf.Unlock()
	if oldHandle != nil {
		return oldHandle.Close()
	}
	return nil
}

// Write b to log file
func (lf *logFile) Write(b []byte) (n int, err error) {
	lf.Lock()
	if lf.handle == nil {
		lf.Unlock()
		return 0, os.ErrInvalid
	}
	n, err = lf.handle.Write(b)
	lf.Unlock()
	lf.curSize += int64(n)
	if lf.maxSize > 0 && lf.curSize > lf.maxSize {
		lf.Rotate()
	}
	return n, err
}

// LogRotator 定时归档日志文件
func (lf *logFile) LogRotator() {
	for lf.handle != nil {
		now := time.Now()
		next := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
		<-time.After(next.Sub(now)) // 每日归档一次
		lf.Rotate()
	}
}

// LogPurge remove old log files
func (lf *logFile) LogPurge() {
	files, _ := filepath.Glob(fmt.Sprintf("%s.*", lf.baseName))
	for i := len(files) - int(lf.maxFiles) - 1; i >= 0; i-- {
		os.Remove(files[i])
	}
}
